LBB

ServiceWork addEventListener('fetch') 在 Chrome 与 Safari 中表现不同

# ServiceWork addEventListener('fetch') 在 Chrome 与 Safari 中表现不同 先决条件: Chrome 120 | Safari 17.4 最近在一个项目中发现了奇怪的问题,请求静态资源有 Cache-Control 的情况下,Chrome 和 Safari 缓存表现不同。 Chrome 中缓存正常 - 刷新。访问静态资源显示 (disk cache) - 退出再访问。同上 Safari 中 - 刷新。访问静态资源会显示 (memory) - 退出再访问。由于缓存在内存里,退出后就没了,导致静态资源重新请求 别的网站,比如百度的静态资源在 Safari 中缓存是 (disk),为什么我们网站的缓存却是在内存里,不能持久化呢? 经过一番排查最终定位,是因为我们项目中用了 Service Work 的 addEventListener('fetch') 导致的。 ## 问题现场 出现问题时,我们的 service worker 脚本大致如下。下面的代码中,我们希望在匹配到某些的 url 时手动管理缓存,对其他请求不做任何处理。 ```ts // service-work.js const CACHE_NAME = 'image-cache-v1' self.addEventListener('fetch', (event) => { const url = new URL(event.request.url) const pathname = decodeURIComponent(url.pathname) if ( // some rule ) { const params = new URLSearchParams() const cacheKey = url.pathname + '?' + params.toString() event.respondWith( caches.open(CACHE_NAME).then((cache) => { return cache.match(cacheKey).then((response) => { return ( response || fetch(event.request).then((networkResponse) => { cache.put(cacheKey, networkResponse.clone()) return networkResponse }) ) }) }) ) }) ``` 根据我们的代码,if 条件外的请求应该与正常的 HTTP 请求缓存行为一致。在 Cache-Control 的控制下,缓存在 disk 上。 Chrome 中没有问题,但在 Safari 中却变成了 memory cache。 这种行为与 https://web.dev/articles/service-worker-caching-and-http-caching 中的介绍不一致,我还没有找到 Safari 这方面的具体文档或解释。 ## 解决方式 最后我们为 addEventListener('fetch') 添加一个兜底,对所有请求都手动调用 fetch,并检查在 Service Work 中的缓存,解决了这个问题。 经测试,手动调用 fetch 后,Safari 的 Service Worker 缓存是可以持久化的。 ```javascript // service-work.js const CACHE_NAME = 'image-cache-v1' self.addEventListener('fetch', (event) => { const url = new URL(event.request.url) const pathname = decodeURIComponent(url.pathname) if ( // some rule ) { const params = new URLSearchParams() params.set('x-oss-process', url.searchParams.get('x-oss-process')) const cacheKey = url.pathname + '?' + params.toString() event.respondWith( caches.open(CACHE_NAME).then((cache) => { return cache.match(cacheKey).then((response) => { return ( response || fetch(event.request).then((networkResponse) => { cache.put(cacheKey, networkResponse.clone()) return networkResponse }) ) }) }) ) } else { // 手动检查缓存 event.respondWith( caches.match(event.request).then(function (response) { return response || fetch(event.request) }) ) } }) ``` ## 结论 当使用 Service Work 监听 fetch 时,在 Safari 上会导致默认请求的缓存写在内存上。如果想避免这种问题,可以在Service Work 中手动调用 fetch 并检查 Service 的缓存。 > 在这次排除 bug 中,还发现在开启了代理的情况下,Safari 也会将资源只缓存到内存中。
ServiceWork addEventListener('fetch') 在 Chrome 与 Safari 中表现不同 | LBB